射频识别(RFID)技术

高级 NFC(翻译)

作者:陈广 日期:2019-11-17


此文档描述了高级 NFC 话题,诸如使用各类标签技术、向 NFC 标签写数据、前台调度,这使得一个应用程序即使在其他应用程序筛选相同的 intent 时,也可以在前台处理此 intent。

使用支持的标签技术

使用 NFC 标签和 Android 设备时,用于读取和写入标签上的数据的主要格式是 NDEF。当设备扫描到携带 NDEF 数据的标签时,Android 支持解析消息,并在可能的情况下在NdefMessage中传递消息。但是在某些情况下,当您扫描不包含 NDEF 数据的标签时,或者 NDEF 数据无法映射为 MIME 类型或 URI 时。在这些情况下,您需要直接与标签进行通信,并使用自己的协议(以原始字节为单位)对其进行读取和写入。Android 通过android.nfc.tech包为这些用例提供了通用支持,如表1所示。可以使用getTechList()方法来确定所支持的标签技术,并使用由android.nfc.tech提供的类创建相应的TagTechnology对象。

表 1:支持的标签技术

描述
TagTechnology 所有标签技术类必须实现的接口
NfcA 提供访问 NFC-A(ISO 14443-3A)的属性和 I/O 操作
NfcB 提供访问 NFC-B(ISO 14443-3B)的属性和 I/O 操作
NfcF 提供访问 NFC-F(JIS 6319-4)的属性和 I/O 操作
NfcV 提供访问 NFC-V(ISO 15693)的属性和 I/O 操作
IsoDep 提供访问 ISO-DEP(ISO 14443-4)的属性和 I/O 操作
Ndef 对于被格式化为 NDEF 的 NFC 标签,提供对 NDEF 数据的访问和操作
NdefFormatable 对可格式化为 NDEF 的标签,提供格式化操作

以下标签技术对于 Android 设备来说,不是必须要支持的。

表 2:可选择支持的标签技术

描述
MifareClassic 如果 Android 设备支持 MIFARE,提供访问 MIFARE Classic 的属性和 I/O 操作。
MifareUltralight 如果 Android 设备支持 MIFARE,提供访问 MIFAREUltralight 的属性和I/O操作。

使用标签技术和 ACTION_TECH_DISCOVERED intent

当设备扫描到包含 NDEF 数据但不能映射到 MIME 或 URI 的标签时,标签调度系统将尝试使用ACTION_TECH_DISCOVERED intent 启动 activity。ACTION_TECH_DISCOVERED还用于非 NDEF 数据的标签。具有此回退功能,可以直接在标签调度系统无法为您分析标签的情况下使用标签上的数据。使用标签技术时的基本步骤如下:

  1. ACTION_TECH_DISCOVERED intent 筛选指定要处理的标签技术。参考“NFC intent 筛选”获取更多信息。通常情况下,当 NDEF 消息无法映射为 MIME 类型或 URI 时,或扫描到的标签不包含 NDEF 数据时,标签调度系统会启动一个ACTION_TECH_DISCOVERED intent。获取如何确定这一点的更多信息,请参考“标签调度系统”。
  2. 当应用程序接收到 intent,从 intent 中获取Tag对象:
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  1. 通过调用android.nfc.tech包中某个类的get工厂方法,获取一个TagTechnology实例。在调用get工厂方法之前,可以通过调用getTechList()来枚举标签所支持的技术。例如,为从Tag中获取MifareUltralight实例,可使用如下方法:
MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));

标签的读写

读取和写入 NFC 标签涉及从 intent 获取标标签并打开与标签的通信。必须定义自己的协议栈才能将数据读写到标签中。但是,请记住,在直接使用标签时,仍然可以读取和写入 NDEF 数据。这取决于你想如何安排事情。下面的示例演示如何使用 MIFARE Ultralight 标签。

package com.example.android.nfc;

import android.nfc.Tag;
import android.nfc.tech.MifareUltralight;
import android.util.Log;
import java.io.IOException;
import java.nio.charset.Charset;

public class MifareUltralightTagTester {

    private static final String TAG = MifareUltralightTagTester.class.getSimpleName();

    public void writeTag(Tag tag, String tagText) {
        MifareUltralight ultralight = MifareUltralight.get(tag);
        try {
            ultralight.connect();
            ultralight.writePage(4, "abcd".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(5, "efgh".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(6, "ijkl".getBytes(Charset.forName("US-ASCII")));
            ultralight.writePage(7, "mnop".getBytes(Charset.forName("US-ASCII")));
        } catch (IOException e) {
            Log.e(TAG, "IOException while writing MifareUltralight...", e);
        } finally {
            try {
                ultralight.close();
            } catch (IOException e) {
                Log.e(TAG, "IOException while closing MifareUltralight...", e);
            }
        }
    }

    public String readTag(Tag tag) {
        MifareUltralight mifare = MifareUltralight.get(tag);
        try {
            mifare.connect();
            byte[] payload = mifare.readPages(4);
            return new String(payload, Charset.forName("US-ASCII"));
        } catch (IOException e) {
            Log.e(TAG, "IOException while reading MifareUltralight message...", e);
        } finally {
            if (mifare != null) {
               try {
                   mifare.close();
               }
               catch (IOException e) {
                   Log.e(TAG, "Error closing tag...", e);
               }
            }
        }
        return null;
    }
}

使用前台调度系统

前台调度系统允许 activity 拦截 intent 并要求优先于其他 activity 处理相同 intent。使用这个系统需要为 Android 系统构建一些数据结构,以便能够向您的应用程序发送适当的 intent。为启用前台调度系统:

  1. 在您的 activity 的onCreate()方法中添加以下代码:

    • 创建一个PendingIntent对象,以便在标签被扫描时 Android 系统可以向其填充详细信息。
    PendingIntent pendingIntent = PendingIntent.getActivity(
        this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    
    • 声明 intent filter 来处理您希望拦截的 intent。前台调度系统根据设备扫描标签时接收到的 intent 检查指定的 intent filter。如果匹配,则应用程序将处理该 intent。如果不匹配,则前台调度系统返回到 intent 调度系统。指定存放 intent filter 和技术筛选器的空数组,指定要对所有返回TAG_DISCOVERED intent 的标签进行筛选。下面的代码片段处理NDEF_DISCOVERED的所有 MIME 类型。你应该只处理你需要的东西。
    IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        try {
            ndef.addDataType("*/*");    /* Handles all MIME based dispatches.
                                        You should specify only the ones that you need. */
        }
        catch (MalformedMimeTypeException e) {
            throw new RuntimeException("fail", e);
        }
    intentFiltersArray = new IntentFilter[] {ndef, };
    
    • 设置应用程序希望处理的标签技术数组。调用Object.class.getName()方法以包含您想要支持的技术的类。
    techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
    
  2. 重写以下的 activity 生命周期回调,并添加逻辑,以便在 activity 失去焦点(onPause())与重新获得焦点(onResume())时启用和禁用前景调度。必须从主线程调用enableForegroundDispatch(),并且只有在 activity 处于前台时才调用(调用onResume()可以保证这一点)。您还需要执行onNewIntent回调以处理来自扫描的 NFC 标签的数据。

public void onPause() {
    super.onPause();
    adapter.disableForegroundDispatch(this);
}

public void onResume() {
    super.onResume();
    adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}

public void onNewIntent(Intent intent) {
    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    //do something with tagFromIntent
}

完整示例,请参考 API Demo 中的 ForegroundDispatch示例。

;

© 2018 - IOT小分队文章发布系统 v0.3